#include "Viewport.h"

namespace Rz { namespace Graphics { namespace Render {

Viewport::Viewport(IRenderDevice* renderDevice, const Math::Rectangle& bounds)
	: _bounds(bounds)
{
	this->SetRenderDevice(renderDevice);
}

void Viewport::Draw()
{
	Scene::Node::Collection scene;
	scene = _sceneVisitor->GetFinalRenderableScene();
	_compositionAdapter->Draw(scene);
}

const CompositionAdapters::ICompositionAdapter* Viewport::GetCompositionAdapter() const 
{
	return _compositionAdapter; 
}

CompositionAdapters::ICompositionAdapter* Viewport::GetCompositionAdapter() 
{ 
	return _compositionAdapter;
}

void Viewport::SetCompositionAdapter(CompositionAdapters::ICompositionAdapter* compositionAdapter)
{
	_compositionAdapter = compositionAdapter;
	_compositionAdapter->SetRenderDevice(this->GetRenderDevice());
	_compositionAdapter->SetViewport(this);
	_compositionAdapter->SetCamera(_cameraNode);
}

const SceneVisitors::ISceneVisitor* Viewport::GetSceneVisitor() const 
{ 
	return _sceneVisitor;
}

SceneVisitors::ISceneVisitor* Viewport::GetSceneVisitor() 
{ 
	return _sceneVisitor; 
}

void Viewport::SetSceneVisitor(SceneVisitors::ISceneVisitor* sceneVisitor)
{
	_sceneVisitor = sceneVisitor;
	_sceneVisitor->SetCamera(_cameraNode);
}

const Scene::CameraNode* Viewport::GetCamera() const
{
	return _cameraNode; 
}

Scene::CameraNode* Viewport::GetCamera() 
{
	return _cameraNode; 
}

void Viewport::SetCamera(Scene::CameraNode* camera)
{
	_cameraNode = camera;

	if (_sceneVisitor != nullptr)
	{
		_sceneVisitor->SetCamera(_cameraNode);
		_compositionAdapter->SetCamera(_cameraNode);
	}
}

Math::Size Viewport::GetSize() const 
{ 
	return _bounds.GetSize();
}

void Viewport::SetSize(const Math::Size& size)
{
	_bounds.SetSize(size);
}

const Math::Rectangle& Viewport::GetBounds() const
{
	return _bounds; 
}

void Viewport::SetBounds(const Math::Rectangle& bounds)
{
	_bounds = bounds;
}

f32 Viewport::GetNear() const
{ 
	return _near; 
}

void Viewport::SetNear(f32 nearPlane) 
{ 
	_near = nearPlane; 
}

f32 Viewport::GetFar() const 
{
	return _far;
}

void Viewport::SetFar(f32 farPlane) 
{ 
	_far = farPlane;
}

Math::Vector3 Viewport::Project(const Math::Vector3& source, const Math::Matrix4& projection, const Math::Matrix4& view, const Math::Matrix4& world) const
{
	Math::Matrix4 matrix = world * view * projection;
	Math::Vector3 vector = matrix * source;
	f32 a = (((source.X * matrix.M14) + (source.Y * matrix.M24)) + (source.Z * matrix.M34)) + matrix.M44;

	vector /= a;

	vector.X = (((vector.X + 1.0f) * 0.5f) * _bounds.GetWidth()) + _bounds.GetX();
	vector.Y = (((-vector.Y + 1.0f) * 0.5f) * _bounds.GetHeight()) + _bounds.GetY();
	vector.Z = (vector.Z * (_far - _near)) + _near;
	return vector;
}

Math::Vector3 Viewport::Unproject(const Math::Vector3& source, const Math::Matrix4& projection, const Math::Matrix4& view, const Math::Matrix4& world) const
{
	Math::Matrix4 matrix = (world * view * projection).Inverted();
	Math::Vector3 sourceCopy = source;
	sourceCopy.X = (((source.X - _bounds.GetX()) / (_bounds.GetWidth())) * 2.0f) - 1.0f;
	sourceCopy.Y = -((((source.Y - _bounds.GetY()) / (_bounds.GetHeight())) * 2.0f) - 1.0f);
	sourceCopy.Z = (source.Z - _near) / (_far - _near);
	Math::Vector3 vector = matrix * sourceCopy;
	f32 a = (((sourceCopy.X * matrix.M14) + (sourceCopy.Y * matrix.M24)) + (sourceCopy.Z * matrix.M34)) + matrix.M44;

	vector /= a;

	return vector;
}

} } }

